/** @file    PluginModule.cpp
 *  @author  Alessandro Polo
 *  @version $Id: PluginModule.cpp 2488 2010-05-21 01:48:27Z alex $
 *  @brief
 * File containing methods for the wosh::plugins::PluginModule class.
 * The header for this class can be found in PluginModule.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include <framework/plugins/PluginModule.h>
 #include <framework/plugins/Plugin.h>

 #include <core/FileSystem.h>
 #include <core/Utilities.h>
 #include <core/WoshKernel.h>


#ifdef _OS_POSIX
  #include <dlfcn.h>

#else
 #include <windows.h>
 #ifdef _OS_WINCE
  #include <winbase.h>
  #include <core/wince/stringutils.h>
 #else
  #include <core/win32/utils.h>
 #endif

 #define stringptr(v)  ((v).empty()?NULL:&(v)[0])

#endif

using namespace std;
namespace wosh {
 namespace plugins {

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

PluginModule::PluginModule() : Object()
 {
	Object::setName( "PluginLoader", Utilities::randomLong() );

	Log.setIndent( 4 );
	Log.setLevel( LOG_INFO );

Log.setLevel( LOG_VERBOSE );

	this->state = PluginModule::STATE_IDLE;
	this->handle = NULL;

	this->library_name = "";
	this->library_uuid = "";
	this->library_type = PluginModule::TYPE_UNKNOWN;

	this->version_mayor = 0.0;
	this->version_minor = 0.0;
 }

PluginModule::~PluginModule()
 {
	if ( this->handle != NULL )
		close();

 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT PluginModule::open()
 {
	if ( this->state & (int)PluginModule::STATE_OPENED ) {
		Log(LOG_CRITICAL, ":open() : FAILED: Library object is already OPENED!" );
		return WRET_ERR_WRONG_STATE;
	 }
	if ( this->library_file.isFile() ) {
		Log(LOG_CRITICAL, ":open() : FAILED: Library filename is NOT valid! [%s]", this->library_file.getPathLocal().c_str() );
		return WRET_ERR_PARAM;
	 }
	if ( this->handle != NULL ) {
		Log(LOG_CRITICAL, ":open() FAILED handle is NOT NULL!" );
		return WRET_ERR_INTERNAL;
	 }

	Log(LOG_VERBOSE, ":open() : Opening library [%s]..", this->library_file.getPathLocal().c_str() );

#ifdef _OS_POSIX
	this->handle = dlopen( this->library_file.getPathLocal().c_str() , RTLD_LAZY);
	if ( this->handle == NULL ) {
		Log(LOG_CRITICAL, ":open() FAILED opening library [%s] : %s", this->library_file.getPathLocal().c_str(), dlerror() );
		return WRET_ERR_INTERNAL;
	 }
#else
    std::basic_string<WCHAR> wstr; 
	wstr.resize(MultiByteToWideChar( CP_UTF8, 0, this->library_file.getPathLocal().c_str(), -1, NULL, 0 )); 
    size_t len= MultiByteToWideChar( CP_UTF8, 0, this->library_file.getPathLocal().c_str(), -1, stringptr(wstr), wstr.size() );
    if ( len > 0 )	wstr.resize(len-1);
    else			wstr.resize(0);

	this->handle = (void*) LoadLibrary( wstr.c_str() );
#endif

	if ( this->handle == NULL ) {
		Log(LOG_CRITICAL, ":open() FAILED opening library [%s]..", this->library_file.getPathLocal().c_str() );
		return WRET_ERR_INTERNAL;
	 }

	this->state = this->state | (int)PluginModule::STATE_OPENED;

	Log(LOG_INFO, ":open() : Opened library [%s]..", this->library_file.getPathLocal().c_str() );
	return WRET_OK;
 }

WRESULT PluginModule::close()
 {
	if ( (this->state & (int)PluginModule::STATE_OPENED) == false ) {
		Log(LOG_CRITICAL, ":close() : FAILED: Library object is NOT opened!" );
		return WRET_ERR_WRONG_STATE;
	 }
	if ( this->handle == NULL ) {
		Log(LOG_CRITICAL, ":close() : FAILED: NULL Handler!" );
		return WRET_ERR_INTERNAL;
	 }
	// check if object are allocated!

	if ( this->objects.size() > 0 ) {
		Log(LOG_VERBOSE, ":close() : Destroying object before closing library [%s]..", this->library_file.getPathLocal().c_str() );
		dealloc();
	 }

	Log(LOG_VERBOSE, ":close() : Closing library [%s]..", this->library_file.getPathLocal().c_str() );

#ifdef _OS_POSIX
	dlclose( this->handle );
#else
	FreeLibrary( (HINSTANCE)this->handle );
#endif

	this->handle = NULL;
	this->state = (int)PluginModule::STATE_IDLE; //bug

	Log(LOG_INFO, ":close() : Closed library [%s]..", this->library_file.getPathLocal().c_str() );
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT PluginModule::initFW()
 {
	if ( (this->state & (int)PluginModule::STATE_OPENED) == false ) {
		Log(LOG_CRITICAL, ":initFW() : FAILED: Library object is NOT opened!" );
		return WRET_ERR_WRONG_STATE;
	 }
	if ( this->handle == NULL ) {
		Log(LOG_CRITICAL, ":initFW() : FAILED: NULL Handler!" );
		return WRET_ERR_INTERNAL;
	 }
	if ( this->state & (int)PluginModule::STATE_INITIALIZED ) {
		Log(LOG_CRITICAL, ":initFW() : FAILED: Library object is NOT opened!" );
		return WRET_ERR_ILLEGAL_USE;
	 }

	init_allocator_t initFunc = NULL;

#ifdef _OS_POSIX
    initFunc = (init_allocator_t) dlsym(this->handle, _PLUGIN_FUNC_INIT);
#else
 #ifdef _OS_WINCE
 #else
	initFunc = (init_allocator_t) GetProcAddress( (HINSTANCE)this->handle, _PLUGIN_FUNC_INIT );
 #endif
#endif

	if ( initFunc == NULL ) {
		Log(LOG_CRITICAL, ":initFW() : FAILED: NULL Function [" _PLUGIN_FUNC_INIT "]!" );
		return NULL;
	 }

	Log(LOG_VERBOSE, ":initFW() : Calling " _PLUGIN_FUNC_INIT ".." );
	initFunc( WoshKernel::getInstance() );

	Log(LOG_INFO, ":initFW() : Called " _PLUGIN_FUNC_INIT ".." );

	this->state = this->state | (int)PluginModule::STATE_INITIALIZED;

	return WRET_OK;
 }


IReflection* PluginModule::alloc( const string& object_type )
 {
	if ( (this->state & (int)PluginModule::STATE_OPENED) == false ) {
		Log(LOG_CRITICAL, ":alloc(%s) : FAILED: Library object is NOT opened!", object_type.c_str() );
		return NULL;
	 }
	if ( this->handle == NULL ) {
		Log(LOG_CRITICAL, ":alloc(%s) : FAILED: NULL Handler!", object_type.c_str() );
		return NULL;
	 }

	if ( (this->state & (int)PluginModule::STATE_INITIALIZED) == false ) {
		Log(LOG_VERBOSE, ":alloc(%s) : Initialization required!" );
		if ( WFAILED( initFW() ) ) {
			Log(LOG_CRITICAL, ":alloc(%s) : FAILED Initializating!" );
			return NULL;
		 }
	 }

	create_object_t coFunc = NULL;

#ifdef _OS_POSIX
    coFunc = (create_object_t) dlsym(this->handle, _PLUGIN_FUNC_CREATE);
#else
 #ifdef _OS_WINCE
 #else
	coFunc = (create_object_t) GetProcAddress( (HINSTANCE)this->handle, _PLUGIN_FUNC_CREATE );
 #endif
#endif

	if ( coFunc == NULL ) {
		Log(LOG_CRITICAL, ":alloc(%s) : FAILED: NULL Function [" _PLUGIN_FUNC_CREATE "]!", object_type.c_str() );
		return NULL;
	 }

	Log(LOG_VERBOSE, ":alloc(%s) : Calling " _PLUGIN_FUNC_CREATE "..", object_type.c_str() );
	IReflection* object = coFunc( object_type.c_str() );
	if ( object == NULL ) {
		Log(LOG_CRITICAL, ":alloc(%s) : FAILED: NULL Object!", object_type.c_str() );
		return NULL;
	 }
///	Log(LOG_INFO, ":alloc(%s) : Allocated [%s]..", object_type.c_str(), object->getEntityID().c_str() );
//	this->objects.set(object->getEntityID(), object);

	return object;
 }

WRESULT PluginModule::dealloc( const string& object_uuid )
 {
	if ( (this->state & (int)PluginModule::STATE_OPENED) == false ) {
		Log(LOG_CRITICAL, ":dealloc(%s) : FAILED: Library object is NOT opened!", object_uuid.c_str() );
		return WRET_ERR_WRONG_STATE;
	 }
	if ( this->handle == NULL ) {
		Log(LOG_CRITICAL, ":dealloc(%s) : FAILED: NULL Handler!", object_uuid.c_str() );
		return WRET_ERR_INTERNAL;
	 }

	destroy_object_t doFunc = NULL;

#ifdef _OS_POSIX
    doFunc = (destroy_object_t) dlsym(this->handle, _PLUGIN_FUNC_DESTROY);
#else
 #ifdef _OS_WINCE
 #else
	doFunc = (destroy_object_t) GetProcAddress( (HINSTANCE)this->handle, _PLUGIN_FUNC_DESTROY );
 #endif
#endif

	if ( doFunc == NULL ) {
		Log(LOG_CRITICAL, ":dealloc(%s) : FAILED: NULL Function [" _PLUGIN_FUNC_DESTROY "]!", object_uuid.c_str() );
		return WRET_ERR_INTERNAL;
	 }

	this->objects.transactionBeginWrite();
	MapT<std::string, Object*>::Iterator it;
	MapT<std::string, Object*>::ConstIterator it_end = this->objects.end();
	for ( it=this->objects.begin(); it!=it_end; ++it ) {
		if ( it->second == NULL ) {
			Log(LOG_WARNING, ":dealloc() WARNING : NULL Object! [%s]", it->first.c_str() );
			continue;
		 }
		if ( !object_uuid.empty() ) {
			if ( it->first != object_uuid )
				continue;
		 }
		Log(LOG_INFO, ":dealloc() : Destroying [%s]..", it->first.c_str() );
		doFunc( it->second );
		this->objects.erase(it, true);
	 }
	this->objects.transactionEnd();

	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT PluginModule::loadInfo( bool load_if_required )
 {
	bool closeLater = false;
	if ( (this->state & (int)PluginModule::STATE_OPENED) == false ) {
		if ( !load_if_required ) {
			Log(LOG_CRITICAL, ":loadInfo() : FAILED: Library object is NOT opened!" );
			return WRET_ERR_WRONG_STATE;
		 }
		WRESULT ret = open();
		if ( WFAILED(ret) )
			return WRET_ERR_INTERNAL;
		closeLater = true;
	 }
	if ( this->handle == NULL ) {
		Log(LOG_CRITICAL, ":loadInfo() : FAILED: NULL Handler!" );
		return WRET_ERR_INTERNAL;
	 }

	PluginInformation info;
	memset(info.library_name, 0, 255);
	memset(info.library_uuid, 0, 255);
	memset(info.library_author, 0, 255);
	info.version_mayor = 0.0;
	info.version_minor = 0.0;
	info.objects_type = NULL;
	info.objects_type_count = 0;

	gather_library_information_t gliFunc = NULL;

#ifdef _OS_POSIX
    gliFunc = (gather_library_information_t) dlsym(this->handle, _PLUGIN_FUNC_GATHER_INFO);
#else
 #ifdef _OS_WINCE
 #else
	gliFunc = (gather_library_information_t) GetProcAddress( (HINSTANCE)this->handle, _PLUGIN_FUNC_GATHER_INFO );
 #endif
#endif

	if ( gliFunc == NULL ) {
		Log(LOG_CRITICAL, ":loadInfo() : FAILED: NULL Function [" _PLUGIN_FUNC_GATHER_INFO "]!" );
		if ( closeLater )
			close();
		return WRET_ERR_INTERNAL;
	 }

	Log(LOG_VERBOSE, ":loadInfo() : Calling " _PLUGIN_FUNC_GATHER_INFO ".." );
	gliFunc( &info );

	Log(LOG_INFO, ":loadInfo() : Updating Module info.." );

	this->library_name.assign(info.library_name);
	this->library_uuid.assign(info.library_uuid);
	this->library_author.assign(info.library_author);
	this->version_mayor = info.version_mayor;
	this->version_minor = info.version_minor;

	if ( info.objects_type != NULL && info.objects_type_count != 0 ) {
		string obj_type;
		this->objects_type.transactionBeginWrite();
		for(int i=0; i<info.objects_type_count; i++) {
			if ( info.objects_type[i] == NULL ) continue;
			obj_type.assign( info.objects_type[i] );
			if ( this->objects_type.exists(obj_type) ) continue;
			this->objects_type.push_back(obj_type);
		 }
		this->objects_type.transactionEnd();
		delete [] info.objects_type;
	 }

	if ( closeLater )
		close();

	this->state = this->state | (int)PluginModule::STATE_PARSED;

	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool PluginModule::supportsObjectType( const string& object_type ) const
 {
	if ( object_type.empty() ) return false;
	return this->objects_type.exists( object_type );
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT PluginModule::setLibraryFile( const string& file )
 {
	if ( this->state & (int)PluginModule::STATE_OPENED ) {
		Log(LOG_CRITICAL, ":setLibraryFile(%s) : FAILED: Library object is already OPENED!", file.c_str() );
		return WRET_ERR_WRONG_STATE;
	 }
	this->library_file.set(file);
	Log(LOG_CRITICAL, ":setLibraryFile(%s) [%s]", file.c_str(), this->library_file.getPathLocal().c_str() );
	return WRET_OK;
 }

WRESULT PluginModule::open( const string& library_file )
 {
	WRESULT ret = setLibraryFile(library_file);
	if ( WFAILED(ret) ) {
		return WRET_ERR_PARAM;
	 }
	return open();
 }

WRESULT PluginModule::dealloc()
 {
	return dealloc("");
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 }; // namespace plugins
}; // namespace wosh
